Skip to content

feat(cli): add openkb feedback to file a prefilled GitHub issue#53

Merged
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command
May 17, 2026
Merged

feat(cli): add openkb feedback to file a prefilled GitHub issue#53
KylinMountain merged 3 commits into
mainfrom
feat/feedback-command

Conversation

@KylinMountain
Copy link
Copy Markdown
Collaborator

@KylinMountain KylinMountain commented May 17, 2026

Summary

Adds openkb feedback — opens a prefilled GitHub issue in the user's browser. No backend, no maintainer-owned token, no gh CLI dependency, one flag.

openkb feedback                            # interactive (stdin)
openkb feedback "openkb add hangs"         # one-liner
openkb feedback --type bug "..."           # tags 'bug' (--type bug|feature|question|other)
echo "..." | openkb feedback               # piped / CI — type defaults to 'other'

The auto-attached Diagnostics block is intentionally tiny — openkb version, Python version, OS, and whether a KB is initialised in cwd. No paths, env vars, or keys.

Why this shape

Alternative Problem
Auto-create via maintainer PAT Secret in source / public client
Hosted feedback API Backend + moderation + GDPR for an OSS CLI
User's gh CLI auth Excludes anyone without gh auth login; also skips the natural "review before Submit" step that a browser flow gives

Prefilled URL → browser is the standard pattern for "file a bug" CLI commands (rustup, cargo, etc.). GitHub renders the issue form with our title / body / label, the user reviews and clicks Submit with their own account.

Behaviour details

  • TTY user: prompts for --type if not given on the command line, then opens browser. URL is also printed for copy-fallback if the browser can't auto-open (headless box, no $BROWSER).
  • Non-TTY user (CI / pipe / SSH): skips the type prompt, defaults to other. Browser open may fail silently — the printed URL is the fallback.
  • webbrowser.open returns False: surfaced via stderr (no browser available — copy the URL above), exits 0. Tested via mock.

Files

  • openkb/cli.pyfeedback command + 3 helpers (_openkb_version, _collect_feedback_diagnostics, _build_feedback_url)
  • tests/test_feedback.py — 19 regression tests: URL shape, type → label mapping, title truncation/prefix rules, empty-dict diagnostics shape, the webbrowser.open=False headless path, the non-TTY --type skip path, version-helper consistency across cli/chat/__init__
  • README.md — one row in the Commands table

Self-review fixes applied during PR

After the initial commit I ran the code-review skill against my own PR and addressed 3 findings:

  1. webbrowser.open return value was discarded — silent success on headless boxes. Now captured and reported.
  2. _openkb_version duplicated logic in 3 places with drifted fallback strings — collapsed to one from openkb import __version__; return __version__ that matches openkb/agent/chat.py.
  3. Type prompt wasn't TTY-gated — same pattern PR feat(cli): add --language flag and prompt to openkb init #48 explicitly fixed; non-TTY would hang or abort. Now uses the existing _stdin_is_tty() helper to default to other in pipes / CI.

Then per discussion the original --print-url and --no-diagnostics flags were removed:

  • --print-url was redundant — the URL is always printed for copy-fallback before webbrowser.open is attempted.
  • --no-diagnostics was an over-correction — the diagnostics block is small enough and useful enough for maintainers that there's no real privacy reason to opt out.

Final surface: one flag (--type) plus the positional message argument.

Test plan

  • 329 tests pass (310 prior + 19 new)
  • Smoke: openkb feedback --type bug "msg" opens browser with title/body/label correct (verified by mock + manual run)
  • Smoke: echo "" | openkb feedback "X" doesn't hang on the type prompt; URL is correct (manual run)
  • Version consistency: cli._openkb_version(), chat._openkb_version(), and __init__.__version__ all return the same string (asserted in test_openkb_version_helper_matches_package_version)

Submitting feedback from a CLI tool with no backend is awkward —
auto-creating issues requires a maintainer-owned token (security
nightmare to ship in source), running an OpenKB-owned API server is
overkill for an OSS CLI, and asking users to authenticate with their
own gh CLI excludes anyone who hasn't installed it.

Workaround: build a GitHub issue URL with title / body / labels
prefilled in query params, and open the user's browser. The user
goes through GitHub's normal flow with their own account — no
backend, no secrets, no auth dance.

Usage:

  openkb feedback                            # interactive (stdin)
  openkb feedback "openkb add hangs"         # one-liner
  openkb feedback --type bug "..."           # tags 'bug' label
  openkb feedback --print-url "..."          # SSH / no-browser env
  openkb feedback --no-diagnostics "..."     # skip env info

The auto-collected diagnostics are deliberately minimal — openkb
version, Python version, OS, whether a KB is initialised in cwd.
No paths, no env vars, no API keys. Users can disable with
--no-diagnostics if even that's too much.

Implementation is ~90 lines in cli.py plus 17 regression tests
covering URL shape, type→label mapping, title truncation, the
--no-diagnostics body shape, and the webbrowser-not-called
contract for --print-url.
Comment thread tests/test_feedback.py
from unittest.mock import patch
from urllib.parse import parse_qs, urlparse

import pytest
@KylinMountain
Copy link
Copy Markdown
Collaborator Author

Code review

Found 3 issues.

  1. webbrowser.open return value ignored — silent failure on headless boxes. webbrowser.open returns False (no Exception) when no browser is available. On a CI runner, SSH session without GUI, or container with no BROWSER env, the command prints "Opening GitHub in your browser to file the issue." and exits 0 — but nothing happened. The fallback URL is printed before the call (good) so the user can copy it, but the success message lies. Fix: if not webbrowser.open(url): click.echo("(no browser available — copy the URL above)", err=True).

    https://github.com/VectifyAI/OpenKB/blob/9ca027042906569196539043eedb11957955f95f/openkb/cli.py#L1273-L1283

  2. _openkb_version() is a third copy of the same logic, and the fallback string has already drifted. The same importlib.metadata.version("openkb") pattern exists in openkb/__init__.py (fallback "0.0.0+unknown") and is already wrapped by openkb/agent/chat.py:112-114 as _openkb_version() that just delegates to from openkb import __version__. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds a third re-implementation with fallback "unknown" — so an editable-install user submitting feedback gets a different version string than the same user in chat. Fix: replace the body with from openkb import __version__; return __version__ to match chat.py.

    OpenKB/openkb/cli.py

    Lines 1150 to 1163 in 9ca0270

    def _openkb_version() -> str:
    """Return the installed openkb package version, or 'unknown' if it
    can't be resolved (e.g. running from an editable checkout that isn't
    on PYTHONPATH as a distribution).
    """
    try:
    from importlib.metadata import version
    return version("openkb")
    except Exception:
    return "unknown"

  3. Interactive --type prompt isn't TTY-gated — repeats the silent-automation-break flagged on PR feat(cli): add --language flag and prompt to openkb init #48. PR feat(cli): add --language flag and prompt to openkb init #48's review explicitly called this out: "Adding a 3rd prompt to openkb init is a silent break for existing automation. Scripts piping printf '\n\n' | openkb init ... now Abort! on the new third prompt" — and the accepted fix was the _stdin_is_tty() helper. PR feat(cli): add openkb feedback to file a prefilled GitHub issue #53 adds another click.prompt(...) at line 1256 with no TTY check, so echo "msg" | openkb feedback hangs (no more piped input for the type prompt) or aborts confusingly. Fix: gate with the existing _stdin_is_tty() helper and fall back to --type other (or require --type explicitly when stdin isn't a TTY).

    OpenKB/openkb/cli.py

    Lines 1254 to 1264 in 9ca0270

    if feedback_type is None:
    feedback_type = click.prompt(
    "Type",
    default="other",
    type=click.Choice(_FEEDBACK_TYPES),
    show_default=True,
    show_choices=True,
    )
    diagnostics = {} if no_diagnostics else _collect_feedback_diagnostics(ctx)

Generated with Claude Code.

1. **webbrowser.open return value silently dropped on headless boxes**
   The command printed "Opening GitHub in your browser..." and exited 0
   even when webbrowser.open returned False (no GUI, no $BROWSER, CI
   runner, container without DISPLAY). Now we capture the return value,
   surface "no browser available — copy the URL above" to stderr, and
   only print "Opened GitHub in your browser." after a confirmed True
   return.

2. **_openkb_version was the third copy of the same logic**
   openkb/__init__.py exports __version__ via importlib.metadata with
   fallback "0.0.0+unknown"; openkb/agent/chat.py wraps it as
   _openkb_version(); cli.py was re-implementing it with fallback
   "unknown" — already drifted. Replaced with the same one-liner used
   in chat.py: `from openkb import __version__; return __version__`.
   Now all three call sites agree.

3. **type prompt hung in non-TTY contexts (regression of PR #48)**
   PR #48 introduced _stdin_is_tty() specifically because adding a
   prompt without a TTY check broke automation pipelines. PR #53's
   second prompt (asking for feedback type) repeated the same pattern.
   Now it skips the prompt when stdin isn't a TTY and falls through to
   `--type other`. Pipes / CI work without flags now:

     echo "..." | openkb feedback "msg"   # works, type=other, no label

4 new regression tests:
   - test_feedback_skips_type_prompt_when_stdin_is_not_a_tty
   - test_feedback_warns_when_webbrowser_open_returns_false
   - test_feedback_confirms_when_webbrowser_open_succeeds
   - test_openkb_version_helper_matches_package_version

331 tests pass (327 prior + 4 new).
The two flags added complexity without changing the day-one user
experience:

- --print-url: URL is already printed before webbrowser.open is
  attempted (the "Copy this URL..." line), so SSH/sandbox users can
  copy it from the terminal regardless of whether the auto-open
  succeeds. The flag was redundant.

- --no-diagnostics: the diagnostics block is intentionally tiny
  (openkb version, Python, OS, kb_initialised yes/no) — no paths,
  usernames, env vars, or keys. Maintainers benefit from having it
  on every issue without exception, and the privacy cost is
  negligible.

Surface shrinks to one flag (--type) plus the positional message.
Help text and README updated. 19 feedback tests still pass (329
total).
@KylinMountain KylinMountain merged commit 97b1ca1 into main May 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant